package ru.amse.tsyganov.jumleditor.graphicaleditor;

import java.util.LinkedList;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;

import ru.amse.tsyganov.jumleditor.commands.Command;
import ru.amse.tsyganov.jumleditor.commands.CommandFactory;
import ru.amse.tsyganov.jumleditor.commands.ViewsManager;
import ru.amse.tsyganov.jumleditor.model.ActivityGraph;
import ru.amse.tsyganov.jumleditor.model.State;
import ru.amse.tsyganov.jumleditor.model.Transition;
import ru.amse.tsyganov.jumleditor.properties.Property;
import ru.amse.tsyganov.jumleditor.properties.PropertyChangeListener;
import ru.amse.tsyganov.jumleditor.view.ModelChangeListener;
import ru.amse.tsyganov.jumleditor.view.SelectedItems;
import ru.amse.tsyganov.jumleditor.view.pref.Colors;
import ru.amse.tsyganov.jumleditor.view.pref.Images;
import ru.amse.tsyganov.jumleditor.view.repr.ActivityGraphView;
import ru.amse.tsyganov.jumleditor.view.repr.StateView;
import ru.amse.tsyganov.jumleditor.view.repr.TransitionView;

public class GraphicalEditor extends Composite {

	private static final int TOOL_BAR_HEIGHT = 25;
	
	class MouseEventsHandler implements MouseListener, MouseMoveListener {
		
		public void mouseDoubleClick(MouseEvent e) {
			assert e != null;
			e.x = coordinates.fromScreeXToModelX(e.x);
			e.y = coordinates.fromScreeYToModelY(e.y);
			editorMode.mouseDoubleClick(e);
			canvas.redraw();
		}
		
		public void mouseDown(MouseEvent e) {
			assert e != null;
			e.x = coordinates.fromScreeXToModelX(e.x);
			e.y = coordinates.fromScreeYToModelY(e.y);
			editorMode.mouseDown(e);			
			canvas.redraw();
			forceFocus();
		}
		
		public void mouseUp(MouseEvent e) {
			assert e != null;
			e.x = coordinates.fromScreeXToModelX(e.x);
			e.y = coordinates.fromScreeYToModelY(e.y);
			editorMode.mouseUp(e);
			canvas.redraw();
		}
		
		public void mouseMove(MouseEvent e) {
			assert e != null;
			e.x = coordinates.fromScreeXToModelX(e.x);
			e.y = coordinates.fromScreeYToModelY(e.y);
			editorMode.mouseMove(e);
			canvas.redraw();
		}		
	}
	
	private final EditorMode addActionStateMode;
	
	private final EditorMode addFinalStateMode;
	
	private final EditorMode addForkJoinStateMode;
	
	private final EditorMode addInitialStateMode;
	
	private final EditorMode addDecisionMode;
	
	private final EditorMode addTransitionMode;
	
	private final EditorMode areaSelectionMode;
	
	private final EditorMode selectionMode;
	
	private final EditorMode textInputMode;
	
	private final Canvas canvas;
	
	private final Text textInputField;
	
	private ActivityGraphView<ActivityGraph> activityGraphView;
	
	private final ToolBar toolBar;
	
	private final ViewsManager viewsManager;
	
	private EditorMode editorMode;
		
	private boolean shiftOrCtrlPressed = false;
	
	private final Coordinates coordinates;
	
	private final PropertyChangeListener propertyChangeListener = 
		new PropertyChangeListener() {

		public void propertyChanged(Property p) {
			canvas.redraw();
		}
		
	};

	public GraphicalEditor(
			Composite parent,
			ActivityGraphView<ActivityGraph> activityGraphView,
			ViewsManager viewsManager) {
				
		super(parent, SWT.NONE);
		
		if (parent == null || activityGraphView == null
			|| viewsManager == null) {
			
			throw new IllegalArgumentException();
		}
		
		this.activityGraphView = activityGraphView;
		
		addPropertyChangedListeners();
		
		this.viewsManager = viewsManager;
		
		coordinates = new Coordinates();
		
		canvas = new Canvas(this, SWT.NONE | SWT.DOUBLE_BUFFERED | 
				SWT.V_SCROLL | SWT.H_SCROLL);
		toolBar = new ToolBar(this, SWT.NONE);
		textInputField = new Text(canvas, SWT.SINGLE | SWT.BORDER);
		
		addActionStateMode = new AddActionStateMode(this);
		addFinalStateMode = new AddFinalStateMode(this);
		addForkJoinStateMode = new AddForkJoinStateMode(this);
		addInitialStateMode = new AddInitialStateMode(this);
		addTransitionMode = new AddTransitionMode(this);
		addDecisionMode = new AddDecisionMode(this);
		areaSelectionMode = new AreaSelectionMode(this);
		selectionMode = new SelectionMode(this);
		textInputMode = new TextInputMode(this);
		
		editorMode = selectionMode;
		
		initToolBar();
		initCanvas();
		
		FormLayout compositeFormLayout = new FormLayout();
		compositeFormLayout.marginHeight = 0;
		compositeFormLayout.marginWidth = 0;
		compositeFormLayout.marginBottom = 0;
		compositeFormLayout.marginLeft = 0;
		compositeFormLayout.marginRight = 0;
		compositeFormLayout.marginTop = 0;
		setLayout(compositeFormLayout);	
	}

	private void addPropertyChangedListeners() {
		for (State s : activityGraphView.getModel().getContents()) {
			StateView<? extends State> stateView = activityGraphView
					.getModelViewMap().getStatesMap().get(s);

			stateView.addPropertyListener(propertyChangeListener);

			for (Transition t : s.getStateVertex().getOutgoing()) {
				TransitionView<? extends Transition> transitionView = activityGraphView
						.getModelViewMap().getTransitionsMap().get(t);

				transitionView.addPropertyListener(propertyChangeListener);
			}
		}
	}
	
	private void initCanvas() {
		
		addKeyListener(new KeyListener() {
			public void keyPressed(KeyEvent e) {
				if (e.keyCode == SWT.SHIFT || e.keyCode == SWT.CTRL){
					shiftOrCtrlPressed = true;
				}
				editorMode.keyPressed(e);
				canvas.redraw();
			}
			public void keyReleased(KeyEvent e) {
				if (e.keyCode == SWT.SHIFT || e.keyCode == SWT.CTRL){
					shiftOrCtrlPressed = false;
				}
				editorMode.keyReleased(e);
				canvas.redraw();
			}
		});
				
		Color lightGrayColor = Colors.getWhiteColor();
		
		canvas.setBackground(lightGrayColor);
		canvas.addPaintListener(new PaintListener() {
			public void paintControl(PaintEvent e) {
				
				coordinates.setMinX(0);
				coordinates.setMinY(0);
				coordinates.setMaxX(0);
				coordinates.setMaxY(0);
				
				activityGraphView.prepaintEvent(e.gc, coordinates);
				
				canvas.getHorizontalBar().setMaximum(
						coordinates.getMaxX() - coordinates.getMinX());
				
				canvas.getVerticalBar().setMaximum(
						coordinates.getMaxY() - coordinates.getMinY());
				
				canvas.getHorizontalBar().setSelection(
						coordinates.getOriginX() - coordinates.getMinX());
				
				canvas.getVerticalBar().setSelection(
						coordinates.getOriginY() - coordinates.getMinY());
				
				activityGraphView.paint(e.gc, coordinates);
				editorMode.paintControl(e, coordinates);
			}
		});
		
		canvas.getVerticalBar().addSelectionListener(new SelectionListener() {

			public void widgetDefaultSelected(SelectionEvent e) {
				widgetSelected(e);
			}

			public void widgetSelected(SelectionEvent e) {
				coordinates.setOriginY(canvas.getVerticalBar().getSelection() +
						coordinates.getMinY());
				canvas.redraw();
			}
			
		});
		
		canvas.getHorizontalBar().addSelectionListener(new SelectionListener() {

			public void widgetDefaultSelected(SelectionEvent e) {
				widgetSelected(e);
			}

			public void widgetSelected(SelectionEvent e) {
				coordinates.setOriginX(canvas.getHorizontalBar().getSelection() + 
						coordinates.getMinX());
				canvas.redraw();
			}
			
		});
		
		canvas.getHorizontalBar().setIncrement(10);
		canvas.getVerticalBar().setIncrement(10);
		
		MouseEventsHandler mouseEventsHandler = new MouseEventsHandler();
		canvas.addMouseListener(mouseEventsHandler);
		canvas.addMouseMoveListener(mouseEventsHandler);
		
		FormData canvasFormData = new FormData();
		canvasFormData.left = new FormAttachment(0, 0);
		canvasFormData.right = new FormAttachment(100, 0);
		canvasFormData.bottom = new FormAttachment(100, 0);
		canvasFormData.top = new FormAttachment(toolBar, 0);
		canvas.setLayoutData(canvasFormData);
		
	}
	
	private void initToolBar() {	
		
		FormData toolBarFormData = new FormData();
		toolBarFormData.left = new FormAttachment(0, 0);
		toolBarFormData.right = new FormAttachment(100, 0);
		toolBarFormData.bottom = new FormAttachment(0, TOOL_BAR_HEIGHT);
		toolBarFormData.top = new FormAttachment(0, 0);
		toolBar.setLayoutData(toolBarFormData);
		
		ToolItem undoItem = new ToolItem(toolBar, SWT.PUSH);
		undoItem.setImage(Images.getUndoEditImage());
		undoItem.setToolTipText("Undo");
		undoItem.addSelectionListener(new SelectionListener() {

			public void widgetDefaultSelected(SelectionEvent e) {
				widgetSelected(e);
			}

			public void widgetSelected(SelectionEvent e) {
				viewsManager.undo();
				canvas.redraw();
			}
			
		});
		ToolItem redoItem = new ToolItem(toolBar, SWT.PUSH);
		redoItem.setImage(Images.getRedoEditImage());
		redoItem.setToolTipText("Redo");
		redoItem.addSelectionListener(new SelectionListener() {

			public void widgetDefaultSelected(SelectionEvent e) {
				widgetSelected(e);
			}

			public void widgetSelected(SelectionEvent e) {
				viewsManager.redo();
				canvas.redraw();
			}
			
		});
		
		final ToolItem selectionModeItem = new ToolItem(toolBar, SWT.PUSH);
		selectionModeItem.setImage(Images.getSelectImage());
		selectionModeItem.setToolTipText("Selection");
		selectionModeItem.addSelectionListener(new SelectionListener() {

			public void widgetDefaultSelected(SelectionEvent e) {
				widgetSelected(e);
			}

			public void widgetSelected(SelectionEvent e) {
				setEditorMode(selectionMode);
			}
			
		});
		
		final ToolItem addActionStateModeItem = new ToolItem(toolBar, SWT.PUSH);
		addActionStateModeItem.setImage(Images.getActivityImage());
		addActionStateModeItem.setToolTipText("Add new action state");
		addActionStateModeItem.addSelectionListener(new SelectionListener() {

			public void widgetDefaultSelected(SelectionEvent e) {
				widgetSelected(e);
			}

			public void widgetSelected(SelectionEvent e) {
				setEditorMode(addActionStateMode);
			}
			
		});
		
		addActionStateMode.addSelectionListener(new EditorModeSelectionListener() {

			public void selected() {
				addActionStateModeItem.setImage(Images.getActivityImageSelected());
			}

			public void unselected() {
				addActionStateModeItem.setImage(Images.getActivityImage());
			}
			
		});
		
		final ToolItem addInitialStateModeItem = new ToolItem(toolBar, SWT.PUSH);
		addInitialStateModeItem.setImage(Images.getInitialStateImage());
		addInitialStateModeItem.setToolTipText("Add initial state");
		addInitialStateModeItem.addSelectionListener(new SelectionListener() {

			public void widgetDefaultSelected(SelectionEvent e) {
				widgetSelected(e);
			}

			public void widgetSelected(SelectionEvent e) {
				setEditorMode(addInitialStateMode);
			}
			
		});
		
		addInitialStateMode.addSelectionListener(new EditorModeSelectionListener() {

			public void selected() {
				addInitialStateModeItem.setImage(Images.getInitialStateImageSelected());
			}

			public void unselected() {
				addInitialStateModeItem.setImage(Images.getInitialStateImage());
			}
			
		});
		
		final ToolItem addFinalStateModeItem = new ToolItem(toolBar, SWT.PUSH);
		addFinalStateModeItem.setImage(Images.getFinalStateImage());
		addFinalStateModeItem.setToolTipText("Add new final state");
		addFinalStateModeItem.addSelectionListener(new SelectionListener() {

			public void widgetDefaultSelected(SelectionEvent e) {
				widgetSelected(e);
			}

			public void widgetSelected(SelectionEvent e) {
				setEditorMode(addFinalStateMode);
			}
			
		});
		
		addFinalStateMode.addSelectionListener(new EditorModeSelectionListener() {

			public void selected() {
				addFinalStateModeItem.setImage(Images.getFinalStateImageSelected());
			}

			public void unselected() {
				addFinalStateModeItem.setImage(Images.getFinalStateImage());
			}
			
		});
		
		final ToolItem addForkJoinStateModeItem = new ToolItem(toolBar, SWT.PUSH);
		addForkJoinStateModeItem.setImage(Images.getJoinImage());
		addForkJoinStateModeItem.setToolTipText("Add new fork/join state");
		addForkJoinStateModeItem.addSelectionListener(new SelectionListener() {

			public void widgetDefaultSelected(SelectionEvent e) {
				widgetSelected(e);
			}

			public void widgetSelected(SelectionEvent e) {
				setEditorMode(addForkJoinStateMode);
			}
			
		});
		
		addForkJoinStateMode.addSelectionListener(new EditorModeSelectionListener() {

			public void selected() {
				addForkJoinStateModeItem.setImage(Images.getJoinImageSelected());				
			}

			public void unselected() {
				addForkJoinStateModeItem.setImage(Images.getJoinImage());				
			}
			
		});
		
		final ToolItem addForkDecisionModeItem = new ToolItem(toolBar, SWT.PUSH);
		addForkDecisionModeItem.setImage(Images.getDecisionForkImage());
		addForkDecisionModeItem.setToolTipText("Add new decision state");
		addForkDecisionModeItem.addSelectionListener(new SelectionListener() {

			public void widgetDefaultSelected(SelectionEvent e) {
				widgetSelected(e);
			}

			public void widgetSelected(SelectionEvent e) {
				setEditorMode(addDecisionMode);
			}
			
		});
		
		addDecisionMode.addSelectionListener(new EditorModeSelectionListener() {

			public void selected() {
				addForkDecisionModeItem.setImage(Images.getDecisionForkImageSelected());
			}

			public void unselected() {
				addForkDecisionModeItem.setImage(Images.getDecisionForkImage());
			}
			
		});
		
		final ToolItem addTransitionModeItem = new ToolItem(toolBar, SWT.PUSH);
		addTransitionModeItem.setImage(Images.getTransitionImage());
		addTransitionModeItem.setToolTipText("Add new transition");
		addTransitionModeItem.addSelectionListener(new SelectionListener() {

			public void widgetDefaultSelected(SelectionEvent e) {
				widgetSelected(e);
			}

			public void widgetSelected(SelectionEvent e) {
				setEditorMode(addTransitionMode);
			}
			
		});
		
		addTransitionMode.addSelectionListener(new EditorModeSelectionListener() {

			public void selected() {
				addTransitionModeItem.setImage(Images.getTransitionImageSelected());
			}

			public void unselected() {
				addTransitionModeItem.setImage(Images.getTransitionImage());
			}
			
		});
		
		assert toolBar != null;
	}
	
	/*package*/ void setEditorMode(EditorMode editorMode) {
		editorMode.initState();
		this.editorMode.fireUnselected();
		this.editorMode = editorMode;
		this.editorMode.fireSelected();
	}

	public ActivityGraphView<ActivityGraph> getActivityGraphView() {
		return activityGraphView;
	}

	/*package*/ Canvas getCanvas() {
		return canvas;
	}

	/*package*/ ViewsManager getViewsManager() {
		return viewsManager;
	}
	
	public void undo() {
		viewsManager.undo();
		canvas.redraw();
	}
	
	public void redo() {
		viewsManager.redo();
		canvas.redraw();
	}
	
	/*package*/ boolean isShiftOrCtrlPressed() {
		return shiftOrCtrlPressed;
	}

	/*package*/ Text getTextInputField() {
		return textInputField;
	}
	
	public boolean addSelectionListener(ItemsSelectionListener listener) {
		return activityGraphView.getSelectedItems().addSelectionListener(listener);
	}
	
	public boolean removeSelectionListener(ItemsSelectionListener listener) {
		return activityGraphView.getSelectedItems().removeSelectionListener(listener);
	}

	/*package*/ EditorMode getAddActionStateMode() {
		return addActionStateMode;
	}

	/*package*/ EditorMode getAddFinalStateMode() {
		return addFinalStateMode;
	}

	/*package*/ EditorMode getAddForkJoinStateMode() {
		return addForkJoinStateMode;
	}

	/*package*/ EditorMode getAddInitialStateMode() {
		return addInitialStateMode;
	}

	/*package*/ EditorMode getAddTransitionMode() {
		return addTransitionMode;
	}

	/*package*/ EditorMode getAreaSelectionMode() {
		return areaSelectionMode;
	}

	/*package*/ EditorMode getTextInputMode() {
		return textInputMode;
	}

	/*package*/ EditorMode getSelectionMode() {
		return selectionMode;
	}

	/*package*/ PropertyChangeListener getPropertyChangeListener() {
		return propertyChangeListener;
	}
	
	public final void setSelectedItems(SelectedItems selectedItems) {
		
		activityGraphView.getSelectedItems().selectedStatesClear();
		activityGraphView.getSelectedItems().selectedTransitionsClear();
		
		for (StateView<? extends State> s : selectedItems.getSelectedStates()) {
			activityGraphView.getSelectedItems().selectedStatesAdd(s);
		}
		
		for (TransitionView<? extends Transition> t : selectedItems.getSelectedTransitions()) {
			activityGraphView.getSelectedItems().selectedTransitionsAdd(t);
		}
		
		canvas.redraw();
	}

	public void setActivityGraphView(
			ActivityGraphView<ActivityGraph> newActivityGraphView) {
		
		for (ModelChangeListener listener : 
			activityGraphView.getModel().getModelChangeListeners()) {
			
			newActivityGraphView.getModel().addModelChangeListener(listener);
		}
		activityGraphView = newActivityGraphView;
		viewsManager.clear();
		addPropertyChangedListeners();
		canvas.redraw();
	}
	
	public void deleteSelectedItems() {
		if (editorMode == textInputMode) {
			return;
		}
		LinkedList<Command> commands = new LinkedList<Command>();
		for (StateView<? extends State> stateView : 
			getActivityGraphView().getSelectedItems().getSelectedStates()) {
			
			commands.add(CommandFactory.createDeleteStateCommand(
					getActivityGraphView(), stateView));
		}
		for (TransitionView<? extends Transition> transitionView : 
			getActivityGraphView().getSelectedItems().getSelectedTransitions()) {
			
			commands.add(CommandFactory.createDeleteTransitionCommand(
					transitionView));
		}
		
		if (commands.size() > 0) {
			Command c = CommandFactory.createCompositeCommand(commands);
				getViewsManager().executeCommand(c);
		}
		canvas.redraw();
	}

	public Coordinates getCoordinates() {
		return coordinates;
	}
}
